// ZipFile.cs
//
// Copyright (c) 2006, 2007 Microsoft Corporation.  All rights reserved.
//
// 
// This class library reads and writes zip files, according to the format
// described by pkware, at:
// http://www.pkware.com/business_and_developers/developer/popups/appnote.txt
//
// This implementation is based on the
// System.IO.Compression.DeflateStream base class in the .NET Framework
// v2.0 base class library.
//
// There are other Zip class libraries available.  For example, it is
// possible to read and write zip files within .NET via the J# runtime.
// But some people don't like to install the extra DLL.  Also, there is
// a 3rd party LGPL-based (or is it GPL?) library called SharpZipLib,
// which works, in both .NET 1.1 and .NET 2.0.  But some people don't
// like the GPL. Finally, there are commercial tools (From ComponentOne,
// XCeed, etc).  But some people don't want to incur the cost.
//
// This alternative implementation is not GPL licensed, is free of cost,
// and does not require J#. It does require .NET 2.0 (for the DeflateStream 
// class).  
// 
// This code is released under the Microsoft Permissive License of OCtober 2006. 
// See the License.txt for details.  
//
// Notes:
// This is at best a cripppled and naive implementation. 
//
// Bugs:
// 1. does not do 0..9 compression levels (not supported by DeflateStream)
// 2. does not do encryption
// 3. no support for reading or writing multi-disk zip archives
// 4. no support for file comments or archive comments
// 5. does not stream as it compresses; all compressed data is kept in memory.
// 6. no support for double-byte chars in filenames
// 7. no support for asynchronous operation
// 
// But it does read and write basic zip files, and it gets reasonable compression. 
//
// NB: PKWare's zip specification states: 
//
// ----------------------
//   PKWARE is committed to the interoperability and advancement of the
//   .ZIP format.  PKWARE offers a free license for certain technological
//   aspects described above under certain restrictions and conditions.
//   However, the use or implementation in a product of certain technological
//   aspects set forth in the current APPNOTE, including those with regard to
//   strong encryption or patching, requires a license from PKWARE.  Please 
//   contact PKWARE with regard to acquiring a license.
// ----------------------
//    
// Fri, 31 Mar 2006  14:43
//
// update Thu, 22 Feb 2007  19:03
//  Fixed a problem with archives that had bit-3 (0x0008) set, 
//  where the CRC, Compressed Size, and Uncompressed size 
//  actually followed the compressed file data. 
//


using System;
using System.IO;

namespace ionic.utils.zip
{


	public class ZipFile : System.Collections.Generic.IEnumerable<ZipEntry>,
		IDisposable
	{
		private string _name;
		public string Name
		{
			get { return _name; }
		}



		// when this is set, we trim the volume (eg C:) off any fully-qualified pathname, 
		// before writing the ZipEntry into the ZipFile. 
		// We default this to true.  This allows Windows Explorer to read the zip archives properly. 
		private bool _TrimVolumeFromFullyQualifiedPaths = true;
		public bool TrimVolumeFromFullyQualifiedPaths
		{
			get { return _TrimVolumeFromFullyQualifiedPaths; }
			set { _TrimVolumeFromFullyQualifiedPaths = value; }
		}

		private System.IO.Stream ReadStream
		{
			get
			{
				if (_readstream == null)
				{
					_readstream = System.IO.File.OpenRead(_name);
				}
				return _readstream;
			}
		}

		private System.IO.FileStream WriteStream
		{
			get
			{
				if (_writestream == null)
				{
					_writestream = new System.IO.FileStream(_name, System.IO.FileMode.CreateNew);
				}
				return _writestream;
			}
		}

		private ZipFile() { }


		#region For Writing Zip Files

		public ZipFile(string NewZipFileName)
		{
			// create a new zipfile
			_name = NewZipFileName;
			if (System.IO.File.Exists(_name))
				throw new System.Exception(String.Format("That file ({0}) already exists.", NewZipFileName));
			_entries = new System.Collections.Generic.List<ZipEntry>();
		}


		public void AddItem(string FileOrDirectoryName)
		{
			AddItem(FileOrDirectoryName, false);
		}

		public void AddItem(string FileOrDirectoryName, bool WantVerbose)
		{
			if (System.IO.File.Exists(FileOrDirectoryName))
				AddFile(FileOrDirectoryName, WantVerbose);
			else if (System.IO.Directory.Exists(FileOrDirectoryName))
				AddDirectory(FileOrDirectoryName, WantVerbose);

			else
				throw new Exception(String.Format("That file or directory ({0}) does not exist!", FileOrDirectoryName));
		}

		public void AddFile(string FileName)
		{
			AddFile(FileName, false);
		}

		public void AddFile(string FileName, bool WantVerbose)
		{
			ZipEntry ze = ZipEntry.Create(FileName);
			ze.TrimVolumeFromFullyQualifiedPaths = TrimVolumeFromFullyQualifiedPaths;
			if (WantVerbose) Console.WriteLine("adding {0}...", FileName);
			_entries.Add(ze);
		}

		public void AddDirectory(string DirectoryName)
		{
			AddDirectory(DirectoryName, false);
		}

		public void AddDirectory(string DirectoryName, bool WantVerbose)
		{
			String[] filenames = System.IO.Directory.GetFiles(DirectoryName);
			foreach (String filename in filenames)
			{
				if (WantVerbose) Console.WriteLine("adding {0}...", filename);
				AddFile(filename);
			}

			String[] dirnames = System.IO.Directory.GetDirectories(DirectoryName);
			foreach (String dir in dirnames)
			{
				AddDirectory(dir, WantVerbose);
			}
		}


		public void Save()
		{
			// an entry for each file
			foreach (ZipEntry e in _entries)
			{
				e.Write(WriteStream);
			}

			WriteCentralDirectoryStructure();
			WriteStream.Close();
			_writestream = null;
		}


		private void WriteCentralDirectoryStructure()
		{
			// the central directory structure
			long Start = WriteStream.Length;
			foreach (ZipEntry e in _entries)
			{
				e.WriteCentralDirectoryEntry(WriteStream);
			}
			long Finish = WriteStream.Length;

			// now, the footer
			WriteCentralDirectoryFooter(Start, Finish);
		}


		private void WriteCentralDirectoryFooter(long StartOfCentralDirectory, long EndOfCentralDirectory)
		{
			byte[] bytes = new byte[1024];
			int i = 0;
			// signature
			UInt32 EndOfCentralDirectorySignature = 0x06054b50;
			bytes[i++] = (byte)(EndOfCentralDirectorySignature & 0x000000FF);
			bytes[i++] = (byte)((EndOfCentralDirectorySignature & 0x0000FF00) >> 8);
			bytes[i++] = (byte)((EndOfCentralDirectorySignature & 0x00FF0000) >> 16);
			bytes[i++] = (byte)((EndOfCentralDirectorySignature & 0xFF000000) >> 24);

			// number of this disk
			bytes[i++] = 0;
			bytes[i++] = 0;

			// number of the disk with the start of the central directory
			bytes[i++] = 0;
			bytes[i++] = 0;

			// total number of entries in the central dir on this disk
			bytes[i++] = (byte)(_entries.Count & 0x00FF);
			bytes[i++] = (byte)((_entries.Count & 0xFF00) >> 8);

			// total number of entries in the central directory
			bytes[i++] = (byte)(_entries.Count & 0x00FF);
			bytes[i++] = (byte)((_entries.Count & 0xFF00) >> 8);

			// size of the central directory
			Int32 SizeOfCentralDirectory = (Int32)(EndOfCentralDirectory - StartOfCentralDirectory);
			bytes[i++] = (byte)(SizeOfCentralDirectory & 0x000000FF);
			bytes[i++] = (byte)((SizeOfCentralDirectory & 0x0000FF00) >> 8);
			bytes[i++] = (byte)((SizeOfCentralDirectory & 0x00FF0000) >> 16);
			bytes[i++] = (byte)((SizeOfCentralDirectory & 0xFF000000) >> 24);

			// offset of the start of the central directory 
			Int32 StartOffset = (Int32)StartOfCentralDirectory;  // cast down from Long
			bytes[i++] = (byte)(StartOffset & 0x000000FF);
			bytes[i++] = (byte)((StartOffset & 0x0000FF00) >> 8);
			bytes[i++] = (byte)((StartOffset & 0x00FF0000) >> 16);
			bytes[i++] = (byte)((StartOffset & 0xFF000000) >> 24);

			// zip comment length
			bytes[i++] = 0;
			bytes[i++] = 0;

			WriteStream.Write(bytes, 0, i);
		}

		#endregion

		#region For Reading Zip Files

		/// <summary>
		/// This will throw if the zipfile does not exist. 
		/// </summary>
		public static ZipFile Read(string zipfilename)
		{
			return Read(zipfilename, false);
		}

		/// <summary>
		/// This will throw if the zipfile does not exist. 
		/// </summary>
		public static ZipFile Read(string zipfilename, bool TurnOnDebug)
		{

			ZipFile zf = new ZipFile();
			zf._Debug = TurnOnDebug;
			zf._name = zipfilename;
			zf._entries = new System.Collections.Generic.List<ZipEntry>();
			ZipEntry e;
			while ((e = ZipEntry.Read(zf.ReadStream, zf._Debug)) != null)
			{
				if (zf._Debug) System.Console.WriteLine("  ZipFile::Read(): ZipEntry: {0}", e.FileName);
				zf._entries.Add(e);
			}

			// read the zipfile's central directory structure here.
			zf._direntries = new System.Collections.Generic.List<ZipDirEntry>();

			ZipDirEntry de;
			while ((de = ZipDirEntry.Read(zf.ReadStream, zf._Debug)) != null)
			{
				if (zf._Debug) System.Console.WriteLine("  ZipFile::Read(): ZipDirEntry: {0}", de.FileName);
				zf._direntries.Add(de);
			}

			return zf;
		}

		public System.Collections.Generic.IEnumerator<ZipEntry> GetEnumerator()
		{
			foreach (ZipEntry e in _entries)
				yield return e;
		}

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}


		public void ExtractAll(string path)
		{
			ExtractAll(path, false);
		}


		public void ExtractAll(string path, bool WantVerbose)
		{
			bool header = WantVerbose;
			foreach (ZipEntry e in _entries)
			{
				if (header)
				{
					System.Console.WriteLine("\n{1,-22} {2,-6} {3,4}   {4,-8}  {0}",
											 "Name", "Modified", "Size", "Ratio", "Packed");
					System.Console.WriteLine(new System.String('-', 72));
					header = false;
				}
				if (WantVerbose)
					System.Console.WriteLine("{1,-22} {2,-6} {3,4:F0}%   {4,-8} {0}",
											 e.FileName,
											 e.LastModified.ToString("yyyy-MM-dd HH:mm:ss"),
											 e.UncompressedSize,
											 e.CompressionRatio,
											 e.CompressedSize);
				e.Extract(path);
			}
		}


		public void Extract(string filename)
		{
			this[filename].Extract();
		}


		public void Extract(string filename, System.IO.Stream s)
		{
			this[filename].Extract(s);
		}


		public ZipEntry this[String filename]
		{
			get
			{
				foreach (ZipEntry e in _entries)
				{
					if (e.FileName == filename) return e;
				}
				return null;
			}
		}

		#endregion




		// the destructor
		~ZipFile()
		{
			// call Dispose with false.  Since we're in the
			// destructor call, the managed resources will be
			// disposed of anyways.
			Dispose(false);
		}

		public void Dispose()
		{
			// dispose of the managed and unmanaged resources
			Dispose(true);

			// tell the GC that the Finalize process no longer needs
			// to be run for this object.
			GC.SuppressFinalize(this);
		}


		protected virtual void Dispose(bool disposeManagedResources)
		{
			if (!this._disposed)
			{
				if (disposeManagedResources)
				{
					// dispose managed resources
					if (_readstream != null)
					{
						_readstream.Dispose();
						_readstream = null;
					}
					if (_writestream != null)
					{
						_writestream.Dispose();
						_writestream = null;
					}
				}
				this._disposed = true;
			}
		}


		private System.IO.Stream _readstream;
		private System.IO.FileStream _writestream;
		private bool _Debug = false;
		private bool _disposed = false;
		private System.Collections.Generic.List<ZipEntry> _entries = null;
		private System.Collections.Generic.List<ZipDirEntry> _direntries = null;
	}

}


// Example usage: 
// 1. Extracting all files from a Zip file: 
//
//     try 
//     {
//       using(ZipFile zip= ZipFile.Read(ZipFile))
//       {
//         zip.ExtractAll(TargetDirectory, true);
//       }
//     }
//     catch (System.Exception ex1)
//     {
//       System.Console.Error.WriteLine("exception: " + ex1);
//     }
//
// 2. Extracting files from a zip individually:
//
//     try 
//     {
//       using(ZipFile zip= ZipFile.Read(ZipFile)) 
//       {
//         foreach (ZipEntry e in zip) 
//         {
//           e.Extract(TargetDirectory);
//         }
//       }
//     }
//     catch (System.Exception ex1)
//     {
//       System.Console.Error.WriteLine("exception: " + ex1);
//     }
//
// 3. Creating a zip archive: 
//
//     try 
//     {
//       using(ZipFile zip= new ZipFile(NewZipFile)) 
//       {
//
//         String[] filenames= System.IO.Directory.GetFiles(Directory); 
//         foreach (String filename in filenames) 
//         {
//           zip.Add(filename);
//         }
//
//         zip.Save(); 
//       }
//
//     }
//     catch (System.Exception ex1)
//     {
//       System.Console.Error.WriteLine("exception: " + ex1);
//     }
//
//
// ==================================================================
//
//
//
// Information on the ZIP format:
//
// From
// http://www.pkware.com/business_and_developers/developer/popups/appnote.txt
//
//  Overall .ZIP file format:
//
//     [local file header 1]
//     [file data 1]
//     [data descriptor 1]  ** sometimes
//     . 
//     .
//     .
//     [local file header n]
//     [file data n]
//     [data descriptor n]   ** sometimes
//     [archive decryption header] 
//     [archive extra data record] 
//     [central directory]
//     [zip64 end of central directory record]
//     [zip64 end of central directory locator] 
//     [end of central directory record]
//
// Local File Header format:
//         local file header signature     4 bytes  (0x04034b50)
//         version needed to extract       2 bytes
//         general purpose bit flag        2 bytes
//         compression method              2 bytes
//         last mod file time              2 bytes
//         last mod file date              2 bytes
//         crc-32                          4 bytes
//         compressed size                 4 bytes
//         uncompressed size               4 bytes
//         file name length                2 bytes
//         extra field length              2 bytes
//         file name                       varies
//         extra field                     varies
//
//
// Data descriptor:  (used only when bit 3 of the general purpose bitfield is set)
//         local file header signature     4 bytes  (0x08074b50)
//         crc-32                          4 bytes
//         compressed size                 4 bytes
//         uncompressed size               4 bytes
//
//
//   Central directory structure:
//
//       [file header 1]
//       .
//       .
//       . 
//       [file header n]
//       [digital signature] 
//
//
//       File header:  (This is ZipDirEntry in the code above)
//         central file header signature   4 bytes  (0x02014b50)
//         version made by                 2 bytes
//         version needed to extract       2 bytes
//         general purpose bit flag        2 bytes
//         compression method              2 bytes
//         last mod file time              2 bytes
//         last mod file date              2 bytes
//         crc-32                          4 bytes
//         compressed size                 4 bytes
//         uncompressed size               4 bytes
//         file name length                2 bytes
//         extra field length              2 bytes
//         file comment length             2 bytes
//         disk number start               2 bytes
//         internal file attributes        2 bytes
//         external file attributes        4 bytes
//         relative offset of local header 4 bytes
//         file name (variable size)
//         extra field (variable size)
//         file comment (variable size)
//
// End of central directory record:
//
//         end of central dir signature    4 bytes  (0x06054b50)
//         number of this disk             2 bytes
//         number of the disk with the
//         start of the central directory  2 bytes
//         total number of entries in the
//         central directory on this disk  2 bytes
//         total number of entries in
//         the central directory           2 bytes
//         size of the central directory   4 bytes
//         offset of start of central
//         directory with respect to
//         the starting disk number        4 bytes
//         .ZIP file comment length        2 bytes
//         .ZIP file comment       (variable size)
//
// date and time are packed values, as MSDOS did them
// time: bits 0-4 : second
//            5-10: minute
//            11-15: hour
// date  bits 0-4 : day
//            5-8: month
//            9-15 year (since 1980)
//
// see http://www.vsft.com/hal/dostime.htm

